
#include <GL/glew.h>
#include "glmmdevice.h"
#include <stdio.h>
#include "vertexdefinition.h"
#include "vertex_buffer_object_gl15.h"

#define TEST_GLV_EXT(maj, min, ver, ext) GLMM_VERSION(maj, min) <= (ver) ? GLMM_Native : glewGetExtension(ext) ? GLMM_Extension : GLMM_None

glmmDevice::glmmDevice ()
{
    glewInit ();
    GetDeviceCapabilities (&caps);
}

GLMMRESULT glmmDevice::GetDeviceCapabilities (glmmDeviceCapabilities* caps)
{
    if (!caps)
      {
        return GLMM_ERROR_NULLOBJECT;
      }


    const GLubyte* glVersion = glGetString (GL_VERSION);
    unsigned glVersionMajor, glVersionMinor;
    sscanf ((const char*) glVersion, "%d.%d", &glVersionMajor, &glVersionMinor);

    const GLubyte* glslVersion = glGetString (GL_SHADING_LANGUAGE_VERSION);
    unsigned glslVersionMajor, glslVersionMinor;
    sscanf ((const char*) glslVersion, "%d.%d", &glslVersionMajor, &glslVersionMinor);



    caps->NonPowerOfTwoTextures = glewGetExtension ("GL_ARB_texture_non_power_of_two") ? GLMM_Native : GLMM_None;
    caps->FrameBufferObjects = glewGetExtension ("GL_ARB_framebuffer_object") ? GLMM_Extension : GLMM_None;
    caps->OpenGLVersion = GLMM_VERSION (glVersionMajor, glVersionMinor);
    caps->ShadingLanguageVersion = GLMM_VERSION (glslVersionMajor, glslVersionMinor);
    caps->VertexShader = TEST_GLV_EXT (2, 0, caps->OpenGLVersion, "GL_ARB_vertex_shader");
    caps->FragmentShader = TEST_GLV_EXT (2, 0, caps->OpenGLVersion, "GL_ARB_fragment_shader");
    caps->VertexBufferObjects = TEST_GLV_EXT (1, 5, caps->OpenGLVersion, "GL_ARB_vertex_buffer_object");

    return GLMM_OK;
}

GLMMRESULT glmmDevice::CreateVertexBuffer (unsigned size, GLMMUSAGE usage, glmmIVertexBuffer** out)
{
    if (out == 0)
      {
        return GLMM_ERROR_NULLOBJECT;
      }

    switch (caps.VertexBufferObjects)
    {
        case GLMM_Native:
        {
            VertexBufferObjectGL15* vbo = new VertexBufferObjectGL15 (false, GLMMDATATYPE_FLOAT);
            vbo->PrepareData (size, usage);
            *out = vbo;
            return GLMM_OK;

        }
        case GLMM_Extension:
        {
            /*
            VertexBufferObjectGL15* vbo = new VertexBufferObjectGL15 (false);
            vbo->PrepareData (size, usage);
             *out = vbo;
             */
            return GLMM_ERROR_CAPNOTSUPPORTED;
        }
        case GLMM_None:
            return GLMM_ERROR_CAPNOTSUPPORTED;
    }

    return GLMM_ERROR_NULLOBJECT;
}

GLMMRESULT glmmDevice::CreateIndexBuffer (unsigned size, GLMMDATATYPE type, GLMMUSAGE usage, glmmIIndexBuffer** out)
{
    if (out == 0)
      {
        return GLMM_ERROR_NULLOBJECT;
      }

    switch (caps.VertexBufferObjects)
    {
        case GLMM_Native:
        {
            VertexBufferObjectGL15* vbo = new VertexBufferObjectGL15 (true, type);
            vbo->PrepareData (size, usage);
            *out = vbo;
            return GLMM_OK;

        }
        case GLMM_Extension:
        {
            /*
            VertexBufferObjectGL15* vbo = new VertexBufferObjectGL15 (false);
            vbo->PrepareData (size, usage);
             *out = vbo;
             */
            return GLMM_ERROR_CAPNOTSUPPORTED;
        }
        case GLMM_None:
            return GLMM_ERROR_CAPNOTSUPPORTED;
    }

    return GLMM_ERROR_NULLOBJECT;
}

GLMMRESULT glmmDevice::CreateVertexDefinition (const GLMMVERTEXDESCRIPTION* desc, glmmIVertexDefinition** out)
{
    if (!out)
      {
        return GLMM_ERROR_NULLOBJECT;
      }

    const GLMMVERTEXDESCRIPTION* vd = desc;
    // count the number of descriptions
    unsigned num = 0;
    for (; vd; num++, vd++) 
      {
        if (desc [num].Stream == 0 && desc[num].Offset == 0 && desc [num].Size == 0 && desc[num].Stride == 0)
          {
            break;
          }
        if (num > 10)
          {
            // FAILSAVE EXIT
            return GLMM_ERROR_INVALIDOPERATOR;
          }
      }

    // create the definition object an fill it
    VertexDefinition* def = new VertexDefinition (num);
    for (unsigned i = 0; i < num; i++)
      {
        def->SetSource (i, desc [i]);
      }

    *out = def;
    return GLMM_OK;
}

GLMMRESULT glmmDevice::SetStreamSource (unsigned stream, glmmIVertexBuffer* source)
{
    vertexStreams[stream] = source;
    return GLMM_OK;
}

GLMMRESULT glmmDevice::SetIndices (glmmIIndexBuffer* indices)
{
    this->indices = indices;
    return GLMM_OK;
}

GLMMRESULT glmmDevice::SetVertexDefinition (glmmIVertexDefinition* definition)
{
    this->definition = definition;
    return GLMM_OK;
}

GLMMRESULT glmmDevice::DrawPrimitives (GLMMPRIMTYPE primType, unsigned first, unsigned size)
{
    GLenum type;
    switch (primType)
    {
        case GLMMPRIMTYPE_POINTS:
            type = GL_POINTS;
            break;
        case GLMMPRIMTYPE_LINES:
            type = GL_LINES;
            break;
        case GLMMPRIMTYPE_TRIANGLES:
            type = GL_TRIANGLES;
            break;
        default:
            return GLMM_ERROR_INVALIDOPERATOR;
    }
    
    if (!definition)
      {
        return GLMM_ERROR_NULLOBJECT;
      }
    
    VertexDefinition* def = static_cast<VertexDefinition*>(definition);
    for (unsigned i=0, j=def->GetNumberOfStreams (); i<j; i++)
      {
        GLMMRESULT res = vertexStreams[i]->Bind ();
        if (res != GLMM_OK)
          {
            return res;
          }
      }

    def->SetupStreams ();
    
    glDrawArrays (type, first, size);
    return GLMM_OK;
}

GLMMRESULT glmmDevice::DrawIndexedPrimitives (GLMMPRIMTYPE primType, unsigned first, unsigned size)
{
    GLenum type;
    switch (primType)
    {
        case GLMMPRIMTYPE_POINTS:
            type = GL_POINTS;
            break;
        case GLMMPRIMTYPE_LINES:
            type = GL_LINES;
            break;
        case GLMMPRIMTYPE_TRIANGLES:
            type = GL_TRIANGLES;
            break;
        default:
            return GLMM_ERROR_INVALIDOPERATOR;
    }
    
    if (!definition)
      {
        return GLMM_ERROR_NULLOBJECT;
      }
    
    VertexDefinition* def = static_cast<VertexDefinition*>(definition);
    for (unsigned i=0, j=def->GetNumberOfStreams (); i<j; i++)
      {
        GLMMRESULT res = vertexStreams[i]->Bind ();
        if (res != GLMM_OK)
          {
            return res;
          }
      }

    def->SetupStreams ();
    
    indices->Bind ();
    
    GLenum dataType;
    switch (indices->GetDataType ())
    {
        case GLMMDATATYPE_UNSIGNED_BYTE:
          dataType = GL_UNSIGNED_BYTE;
          break;
        case GLMMDATATYPE_BYTE:
          dataType = GL_BYTE;
          break;
        case GLMMDATATYPE_UNSIGNED_SHORT:
          dataType = GL_UNSIGNED_SHORT;
          break;
        case GLMMDATATYPE_SHORT:
          dataType = GL_SHORT;
          break;
        case GLMMDATATYPE_UNSIGNED_INT:
          dataType = GL_UNSIGNED_INT;
          break;
        case GLMMDATATYPE_INT:
          dataType = GL_INT;
          break;
        default:
          return GLMM_ERROR_INVALIDOPERATOR;
    }

    glDrawElements (type, size, dataType, 0); //(void*)(first));
    return GLMM_OK;
}